package com.cskt.securityproject.config;

import com.cskt.securityproject.entity.Permission;
import com.cskt.securityproject.entity.User;
import com.cskt.securityproject.filter.ValidateCodeFilter;
import com.cskt.securityproject.mapper.PermissionMapper;
import com.cskt.securityproject.mapper.UserMapper;
import com.cskt.securityproject.service.MyUserDetailService;
import jakarta.annotation.Resource;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.authentication.LockedException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityCustomizer;
import org.springframework.security.config.annotation.web.configurers.*;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.rememberme.JdbcTokenRepositoryImpl;
import org.springframework.security.web.authentication.rememberme.PersistentTokenRepository;
import org.springframework.util.ObjectUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import javax.sql.DataSource;
import javax.xml.transform.sax.SAXTransformerFactory;
import java.io.IOException;
import java.util.AbstractCollection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * @description 自定义security配置
 * @date 2023/12/13 10:25
 * @auth mo
 */
@Configuration
@EnableWebSecurity
public class SecurityConfiguration {

    private static final Logger log = LoggerFactory.getLogger(SecurityConfiguration.class);

    @Resource
    private MyUserDetailService myUserDetailService;
    @Resource
    private PermissionMapper permissionMapper;
    @Resource
    private DataSource dataSource;
    @Resource
    private ValidateCodeFilter validateCodeFilter;

    @Bean
    public WebSecurityCustomizer customizer() {
        return web -> web
                .ignoring()
                .requestMatchers("/css/**", "/js/**", "/images/**", "/favicon.ico");
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity httpSecurity) throws Exception {

        httpSecurity
                //暂时禁用跨域请求伪造
                .csrf(AbstractHttpConfigurer::disable)
                //配置同源引用
                .headers(
                        header -> header
                                .frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin)
                );

        //from表单登录
        httpSecurity.formLogin(
                formLogin -> formLogin
                        .loginPage("/toLoginPage") //自定义跳转登录页面的路径，如果登录页面可以直接访问，则只需要写登录页面文件名
                        .loginProcessingUrl("/login") //处理登录请求的路径，默认是“/login” 如果不一致则需要配置
                        .usernameParameter("account") //用户名key,默认是“username” 如果不一致则需要配置
                        .passwordParameter("password") //密码key，默认是“password” 如果不一样则需要配置
                        .defaultSuccessUrl("/") //登录成功后的跳转
                        .failureUrl("/toLoginPage") //登录失败后跳转
        );

        //自定义认证、权限规则
        List<Permission> permissions = permissionMapper.selectPermissionList(); //获取所有的权限
        httpSecurity.authorizeHttpRequests(
                authorizeHttpRequest -> {
                    authorizeHttpRequest.requestMatchers("/toLoginPage","/code/**").permitAll(); //放行登录页面

                    //自定义权限绑定，将权限与请求路径一一映射（光登录还不行，还必须有相应的权限才可以）
                    permissions.forEach(permission -> {
                        //每一个请求路径
                        authorizeHttpRequest.requestMatchers(permission.getPermissionUrl())
                                //必须有相应的权限才可以访问
                                .hasAuthority(permission.getPermissionTag());
                    });

                    authorizeHttpRequest.anyRequest().authenticated(); //其他的所有请求全部必须经过验证，该条配置必须放到最后
                }
        );

        //自定义异常处理逻辑
        httpSecurity.exceptionHandling(exceptionHandling ->
                exceptionHandling.accessDeniedHandler(
                        (request, response, accessDeniedException) -> {
                            log.warn("权限异常：{}", accessDeniedException.getMessage());
                            response.setStatus(HttpServletResponse.SC_FORBIDDEN);
                            response.setContentType("text/html;charset=UTF-8");
                            response.getWriter().println("权限不足，请联系管理员");
                        }
                )
        );

        //记住我功能
        httpSecurity.rememberMe(rememberMe ->
                rememberMe.tokenValiditySeconds(2000)
                        .rememberMeParameter("remember-me")
                        //.tokenRepository(tokenRepository())
        );

        //整合数据库实现自定义登录
        httpSecurity.userDetailsService(myUserDetailService);

        //添加自定义过滤器，将其放在UserNamePasswordAuthenticationFilter前面
        httpSecurity.addFilterBefore(validateCodeFilter, UsernamePasswordAuthenticationFilter.class);


        return httpSecurity.build();

    }

    @Bean
    public PersistentTokenRepository tokenRepository() {
        JdbcTokenRepositoryImpl tokenRepository = new JdbcTokenRepositoryImpl();
        tokenRepository.setDataSource(dataSource);
        //启动的时候是否创建记录token的表，一般只在项目初次启动时设置为true
        tokenRepository.setCreateTableOnStartup(false);
        return tokenRepository;
    }

    /**
     * 指定密码加密方式
     *
     * @return
     */
    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

}
